-module(test_executor).
-export([start/3, init/3, stop/0]).
-include("definitions.hrl").
-define(GRID, 1).
%-spec start/3 :: (pid(), [{string(), integer()}], dict(integer(), integer())) -> ok.
start(Pid, DeviceList, Tests) ->
  register(testExecutor, spawn(test_executor, init, [DeviceList, Tests, self()])),
  %{?MAIN_NODE, Pid} ! {ok, started}
  receive
    {ok, testExecutorStarted} ->
      Pid ! {ok, testsRunning}
  end.

init(DeviceList, Tests, Pid) ->
  state_storage:start(self()),
  process_flag(trap_exit, true),
  {Workers, Pids} = runOn(DeviceList, Tests, dict:new(), dict:new()),
  %Wx = wx:new(),
  %WxStateFrame = wxFrame:new(Wx, -1, ""),
  %wxFrame:setSize(WxStateFrame, {1000, 700}),
  %wxFrame:setTitle(WxStateFrame, "Тесты выполняются"),
  %Grid = initControls(WxStateFrame),
  %initCallbacks(WxStateFrame),
  %wxFrame:show(WxStateFrame),
  Start = os:timestamp(),
  State = #testingState{devicesCount = 0, devicesFinished = 0, reported = false, finish = Start, start = Start},
  Pid ! {ok, testExecutorStarted},
  loopEvent(Workers, Pids, Tests, State)
%,wx:destroy()
.

%% initCallbacks(WxStateFrame) ->
%%   wxFrame:connect(WxStateFrame, close_window).

%-spec initControls/1 :: (wxFrame()) -> wxGrid().
%% initControls(WxStateFrame) ->
%%   %%Button = wxButton:new(WxStateFrame, ?BUTTON_START, [{label, "SHOW"}, {pos, {30, 30}}, {size, {50, 50}}]),
%%   Grid = wxGrid:new(WxStateFrame, ?GRID, [{size, {700, 600}}]),
%%   wxGrid:createGrid(Grid, 0, 0),
%%   wxGrid:autoSizeRows(Grid),
%%   wxGrid:autoSizeColumns(Grid),
%%   Grid
%% .


runOn([], _, Workers, Pids) -> {Workers, Pids};

runOn([{_, DeviceId} | DevicesTail], Tests, Workers, Pids) ->
  {WorkersNew, PidsNew} = case dict:is_key(DeviceId, Tests) of
                            true ->
                              TestId = hd(dict:fetch(DeviceId, Tests)),
                              NewPid = spawn_link(worker, start, [self(), DeviceId, TestId, false]),
                              {dict:append(DeviceId, NewPid, Workers), dict:append(NewPid, DeviceId, Pids)};
                            false -> {Workers, Pids}%тест не назначен
                          end,
  runOn(DevicesTail, Tests, WorkersNew, PidsNew).

runOnafterFault([], _, Workers, Pids) -> {Workers, Pids};

runOnafterFault([{_, DeviceId} | DevicesTail], Tests, Workers, Pids) ->
  {WorkersNew, PidsNew} = case dict:is_key(DeviceId, Tests) of
                            true ->
                              TestId = hd(dict:fetch(DeviceId, Tests)),
                              NewPid = spawn_link(worker, start, [self(), DeviceId, TestId, true]),
                              {dict:append(DeviceId, NewPid, Workers), dict:append(NewPid, DeviceId, Pids)};
                            false -> {Workers, Pids}%тест не назначен
                          end,
  runOnafterFault(DevicesTail, Tests, WorkersNew, PidsNew).

%-spec loopEvent/5 :: (wxFrame(), wxGrid(), dict(pid(), integer()), dict(integer(), pid()), [testCase], #execState) -> no_return().
%Pids[Pid]->DeviceId
%Workers[DeviceId]->Pid
loopEvent(Workers, Pids, Tests, ExecutorState) ->
  receive
    {ok, State} ->
      %io:format("ok State~n"),
      state_storage:updateState(State),
      loopEvent(Workers, Pids, Tests, ExecutorState);
    {update, State} ->
      state_storage:updateState(State),
      loopEvent(Workers, Pids, Tests, ExecutorState);
    {'EXIT', FromPid, Reason} ->%worker упал
      %io:format("~n fell: ~p  ", [pid_to_list(FromPid)]),
      %io:format("~p~n", [Reason]),
      case Reason of%проверить, надо ли поднимать
        normal ->
          {Wnew, Pnew, NewExecutorState} = handleNormalEnding(Workers, Pids, Tests, ExecutorState, FromPid),
          loopEvent(Wnew, Pnew, Tests, NewExecutorState);
        errorDevice ->
          %state_storage:flush(),
          Device = hd(dict:fetch(FromPid, Pids)),
          {TestId, _} = string:to_integer(hd(dict:fetch(Device, Tests))),
          %State = worker:getRestoredState(Device, TestId),
          State = worker:getRestoredStateFromEts(Device),
          case State#stateRecord.completed of
            true ->
              loopEvent(Workers, Pids, Tests, ExecutorState);
            false ->
              % io:format(" trying to lift~n"),
              {WorkersNew, PidsNew} = runOnafterFault([{"loopEvent temp", Device}], Tests, Workers, Pids),
              loopEvent(WorkersNew, PidsNew, Tests, ExecutorState)
          end
      end;

    stop ->
      Devices = dict:fetch_keys(Workers),
      stopWorkers(Devices, Workers),
      io:format("testExecutor finished~n"),
      state_storage:stop()
  end,
  ok.

handleNormalEnding(Workers, Pids, Tests, ExecutorState, FromPid) ->
  Device = hd(dict:fetch(FromPid, Pids)),
  Wnew = dict:erase(Device, Workers),
  Pnew = dict:erase(FromPid, Pids),
  Size = dict:size(Wnew),
  if
    Size == 0 ->% and not ExecutorState#testingState.reported
      Finish = os:timestamp(),
      NewExState = ExecutorState#testingState{reported = true, finish = Finish},
      Start = ExecutorState#testingState.start,
      io:format(ExecutorState#testingState.message, [timer:now_diff(Finish, Start) div 1000]),
      info_ui:stats(),
      {Wnew, Pnew, NewExState};
    true ->
      {Wnew, Pnew, ExecutorState}
  end.

stopWorkers([], _) ->
  ok;
stopWorkers([W | Rest], Workers) ->
  Worker = dict:fetch(W, Workers),
  worker:stop(Worker),
  stopWorkers(Rest, Workers).

stop() ->
  try whereis(testExecutor) of
    undefined -> ok;
    Pid -> Pid ! stop
  catch
    error:_ ->
      io:format("testExecutor is already finished~n")
  end.